package org.zjt.javassist;

import com.sun.org.apache.bcel.internal.util.ClassPath;
import javassist.*;
import javassist.bytecode.AccessFlag;
import javassist.bytecode.ClassFile;
import javassist.bytecode.ConstantAttribute;
import javassist.bytecode.FieldInfo;
import lombok.extern.slf4j.Slf4j;

import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

/**
 * Description:
 *
 *          1、make 多为新建一个类
 *          2、loader 加载main方法
 *          3、clazz 解冻  clazz.defrost();
 *          4、ClassFile 相当于class 文件的修改器。不提供加载功能。
 *          5、字节码直接操作
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *  方法中的参数：
 *
             $0	    null。
             $1	    显式转换其类型的值。
             $_	    结果表达式的值。类型与显式转换后$_的类型相同
             ，即包围的类型( )。

             $r	    显式转换后的类型，或者包围的类型( )。
             $type	甲java.lang.Class代表同一类型的对象$r。
             $proceed    	执行原始类型转换的虚方法的名称。
                            它接受类型的一个参数，java.lang.Object 并
                            在原始表达式指定的显式类型转换之后返回它 。

 *
 * <a href="http://www.javassist.org/tutorial/tutorial.html">官方文档</a>
 * @author juntao.zhang
 * Date: 2018-07-08 下午1:48
 * @see
 */
@Slf4j
public class RedefinedByteCodeGenerator {

    private static final String CLASS_FILE_PARENT = "/Users/juntao.zhang/dev/workspace/myproject/java-basic/src/main/java/";

    public static void main(String[] args) throws Throwable {




//        Class<?> aClass = new SimpleClassLoader().loadClass("org.zjt.javassist.Student");





        replaceMethodBody("org.zjt.javassist.Student", "execute", "System.out.println(\"this method is changed dynamically!\");");
        Student student = new Student();
        student.execute();
    }

    /**
     * 修改类中的方法
     * @param clazzName
     * @param methodName
     * @param newMethodBody
     */
    public static void replaceMethodBody(String clazzName, String methodName, String newMethodBody) {
        try {
            CtClass clazz = ClassPool.getDefault().get(clazzName);
            CtMethod method = clazz.getDeclaredMethod(methodName);
            method.setBody(newMethodBody);
            method.insertBefore("System.out.println(\"insert method before!\");");
            method.insertAfter("System.out.println(\"insert method after!\");");
            clazz.writeFile(CLASS_FILE_PARENT);
            clazz.toClass();
        } catch (NotFoundException | CannotCompileException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    /**
     * 添加方法
     * @param pool
     * @param cc
     * @throws Exception
     */
    public static void addMethod(ClassPool pool,CtClass cc) throws Exception {
        //添加方法
        CtMethod ctMethod = CtNewMethod.make(pool.get("java.lang.String"),"getName",new CtClass[0],new CtClass[0],"return name;",cc);
        ctMethod.setModifiers(Modifier.PUBLIC);
        cc.addMethod(ctMethod);


        CtMethod m = CtNewMethod.make("public int xmove(int dx) { return x += 1; }", cc);
        cc.addMethod(m);
    }


    /**
     * 添加属性
     * @param pool
     * @param cc
     * @throws Exception
     */
    public static void addField(ClassPool pool,CtClass cc) throws Exception {
        CtField name = new CtField(pool.get("java.lang.String"), "name", cc);
        name.setModifiers(Modifier.PRIVATE);
        name.setAttribute("ssss","sssssssssss".getBytes());
        cc.addField(name);
    }


    /**
     * 创建一个类
     * @param pool
     * @throws Exception
     */
    public static void newClass(ClassPool pool) throws Exception {

        /** 创建新的类 **/
        CtClass cc = pool.makeClass("org.zjt.javassist.Point");

        addField(pool,cc);
        addMethod(pool,cc);

        /** 写出class字节码到文件中 **/
        cc.writeFile(CLASS_FILE_PARENT);

        //访问属性
        Class aClass = cc.toClass();
        Field name1 = aClass.getDeclaredField("name");
        name1.setAccessible(Boolean.TRUE);
        Object object = aClass.newInstance();
        name1.set(object,"zjamgsam");
        log.info(String.valueOf(name1.get(object)));


        // 执行方法
        Method getName = aClass.getDeclaredMethod("getName");
        Object invoke = getName.invoke(object);
        log.info(String.valueOf(invoke));
    }


    /**
     * 利用ClassPool 新增 classPath 、实现 parent classloader 双亲委托
     */
    public static class SimpleClassLoader extends ClassLoader{

        private final ClassPool pool;

        public SimpleClassLoader() throws NotFoundException {
            this.pool = new ClassPool();
            pool.appendSystemPath();
            pool.insertClassPath("/Users/juntao.zhang/Desktop/工作/class");
        }

        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            try {
                CtClass ctClass = pool.get(name);
                byte[] bytes = ctClass.toBytecode();
                return defineClass(name,bytes,0,bytes.length);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }


    /**
     * 类加载时候的转换器,可以监听Loader启动和类加载事件
     * @param args
     * @throws Throwable
     */
    public static void loaderTranslatorClass(String[] args) throws Throwable {
        ClassPool pool = ClassPool.getDefault();
        Loader loader = new Loader();
        loader.addTranslator(pool,new SimpleTranslator());
        loader.run("org.zjt.immutable.Operation",args);
    }


    @Slf4j
    public static class SimpleTranslator implements Translator{

        @Override
        public void start(ClassPool pool) throws NotFoundException, CannotCompileException {
            log.info("---------系统 loader 启动时候 start-----------");
        }

        @Override
        public void onLoad(ClassPool pool, String classname) throws NotFoundException, CannotCompileException {
            log.info("---------onLoad  类初始化时候 ----------- classname:{}",classname);
        }
    }


    /**
     * 读取类的字节码信息到ClassFile中
     * @param path
     * @throws Exception
     */
    public static void classFile2Class(String path) throws Exception{
        BufferedInputStream fin = new BufferedInputStream(new FileInputStream(path + "Point.class"));
        ClassFile cf = new ClassFile(new DataInputStream(fin));
        CtClass ctClass = ClassPool.getDefault().makeClass(fin);
    }


    /**
     * 利用ClassFile修改并输出类字节码
     * @param path
     * @throws Exception
     */
    public static void class2ClassFile(String path) throws Exception{
        ClassFile cf = new ClassFile(false,"test.Foo",null);

        cf.setInterfaces(new String [] {"java.lang.Cloneable"});

        FieldInfo f = new FieldInfo(cf.getConstPool(),"width","I");
        f.setAccessFlags(AccessFlag.PUBLIC);
        cf.addField(f);



        cf.write(new DataOutputStream(new FileOutputStream("Foo.class")));
    }
}
